#pragma once

#include "maccess.h"

void memory_reset();
void a1000_reset();

#define MEMSTAT

#ifdef MEMSTAT
class MemStat
{
    public:

        static ULONGLONG WordReads;
        static ULONGLONG LongReads;

        static ULONGLONG WordWrites;
        static ULONGLONG LongWrites;
};
#endif

#ifdef JIT
extern int special_mem;
    #define S_READ 1
    #define S_WRITE 2
extern byte* cache_alloc(int);
extern void cache_free(byte*);
#endif

// int init_shm();
// void preinit_shm();
// extern bool canbang;
// extern int candirect;

#ifdef ADDRESS_SPACE_24BIT
    #define MEMORY_BANKS 256
    #define MEMORY_RANGE_MASK ((1 << 24) - 1)
#else
    #define MEMORY_BANKS 65536
    #define MEMORY_RANGE_MASK (~0)
#endif

typedef uint (REGPARAM3 * mem_get_func)(uae_ptr) REGPARAM;
typedef void (REGPARAM3 * mem_put_func)(uae_ptr, uint) REGPARAM;
typedef byte*(REGPARAM3 * xlate_func)(uae_ptr) REGPARAM;
typedef int (REGPARAM3 * check_func)(uae_ptr, uint) REGPARAM;

// extern byte* address_space, * good_address_map;
extern byte* chipmemory;

extern uint allocated_chipmem;
extern uint allocated_fastmem;
extern uint allocated_slowmem;
extern uint allocated_gfxmem;
extern uint allocated_z3fastmem /*, allocated_z3fastmem2, allocated_z3chipmem*/;
extern uint max_z3fastmem;
// extern uint allocated_a3000mem;
// extern uint allocated_cardmem;

extern uint wait_cpu_cycle_read(uae_ptr addr, int mode);
extern void wait_cpu_cycle_write(uae_ptr addr, int mode, uint v);
extern uint wait_cpu_cycle_read_ce020(uae_ptr addr, int mode);
extern void wait_cpu_cycle_write_ce020(uae_ptr addr, int mode, uint v);

// #undef DIRECT_MEMFUNCS_SUCCESSFUL
// #include "maccess.h"

#define chipmem_start 0x00000000
#define slowmem_start 0x00C00000
#define cardmem_start 0x00E00000
#define kickmem_start 0x00F80000
extern uae_ptr z3fastmem_start /*, z3fastmem2_start, z3chipmem_start*/;
extern uae_ptr p96ram_start;
extern uae_ptr fastmem_start;
extern uae_ptr a3000lmem_start, a3000hmem_start;

extern bool ersatzkickfile;
extern bool /*cloanto_rom,*/ kickstart_rom;
extern ushort kickstart_version;
extern bool uae_boot_rom;
extern int uae_boot_rom_size;
extern uae_ptr rtarea_base;

extern byte* baseaddr[];

enum
{
    ABFLAG_UNK= 0,
    ABFLAG_RAM= 1,
    ABFLAG_ROM= 2,
    ABFLAG_ROMIN= 4,
    ABFLAG_IO= 8,
    ABFLAG_NONE= 16,
    ABFLAG_SAFE= 32
};

struct addrbank
{
    /* These ones should be self-explanatory... */
    mem_get_func lget, wget, bget;
    mem_put_func lput, wput, bput;
    /* Use xlateaddr to translate an Amiga address to a byte * that can
     * be used to address memory without calling the wget/wput functions.
     * This doesn't work for all memory banks, so this function may call
     * abort(). */
    xlate_func xlateaddr;
    /* To prevent calls to abort(), use check before calling xlateaddr.
     * It checks not only that the memory bank can do xlateaddr, but also
     * that the pointer points to an area of at least the specified size.
     * This is used for example to translate bitplane pointers in custom.c */
    check_func check;
    /* For those banks that refer to real memory, we can save the whole trouble
       of going through function calls, and instead simply grab the memory
       ourselves. This holds the memory address where the start of memory is
       for this particular bank. */
    byte* baseaddr;
    TCHAR* name;
    /* for instruction opcode/operand fetches */
    mem_get_func lgeti, wgeti;
    int flags;
};

#define CE_MEMBANK_FAST 0
#define CE_MEMBANK_CHIP 1
#define CE_MEMBANK_CIA 2
#define CE_MEMBANK_FAST16BIT 3

extern byte ce_banktype[65536], ce_cachable[65536];

extern byte* filesysory;
extern byte* rtarea;

extern addrbank chipmem_bank;
extern addrbank chipmem_agnus_bank;
extern addrbank chipmem_bank_ce2;
extern addrbank kickmem_bank;
extern addrbank custom_bank;
extern addrbank clock_bank;
extern addrbank cia_bank;
extern addrbank rtarea_bank;
extern addrbank expamem_bank;
extern addrbank fastmem_bank;
extern addrbank gfxmem_bank;
extern addrbank gayle_bank;
extern addrbank gayle2_bank;
extern addrbank mbres_bank;
extern addrbank akiko_bank;
extern addrbank cardmem_bank;

extern void rtarea_init();
extern void rtarea_init_mem();
extern void rtarea_setup();
extern void expamem_init();
extern void expamem_reset();
extern void expamem_next();

extern uint gfxmem_start;
extern byte* gfxmemory;
extern uint gfxmem_mask;
extern ushort last_custom_value1;

/* Default memory access functions */

extern int REGPARAM3 default_check(uae_ptr addr, uint size) REGPARAM;
extern byte* REGPARAM3 default_xlate(uae_ptr addr) REGPARAM;
/* 680x0 opcode fetches */
extern uint REGPARAM3 dummy_lgeti(uae_ptr addr) REGPARAM;
extern uint REGPARAM3 dummy_wgeti(uae_ptr addr) REGPARAM;

#define bankindex(addr) (((uae_ptr)(addr)) >> 16)

extern addrbank* mem_banks[MEMORY_BANKS];

#ifdef JIT
extern byte* baseaddr[MEMORY_BANKS];
#endif

#define get_mem_bank(addr) (*mem_banks[bankindex(addr)])

#ifdef JIT
    #define put_mem_bank(addr, b, realstart) do { \
        (mem_banks[bankindex(addr)] = (b)); \
        if ((b)->baseaddr) \
            baseaddr[bankindex(addr)] = (b)->baseaddr - (realstart); \
        else \
            baseaddr[bankindex(addr)] = (byte*)(((byte*)b) + 1); \
} while (0)
#else
    #define put_mem_bank(addr, b, realstart) \
    (mem_banks[bankindex(addr)] = (b));
#endif

extern void memory_init();
extern void memory_cleanup();
extern void map_banks(addrbank* bank, int first, int count, int realsize);
extern void map_overlay(int chip);
extern void memory_hardreset();
extern void free_fastmemory();

#define longget(addr) (call_mem_get_func(get_mem_bank(addr).lget, addr))
#define wordget(addr) (call_mem_get_func(get_mem_bank(addr).wget, addr))
#define byteget(addr) (call_mem_get_func(get_mem_bank(addr).bget, addr))
#define longgeti(addr) (call_mem_get_func(get_mem_bank(addr).lgeti, addr))
#define wordgeti(addr) (call_mem_get_func(get_mem_bank(addr).wgeti, addr))
#define longput(addr, l) (call_mem_put_func(get_mem_bank(addr).lput, addr, l))
#define wordput(addr, w) (call_mem_put_func(get_mem_bank(addr).wput, addr, w))
#define byteput(addr, b) (call_mem_put_func(get_mem_bank(addr).bput, addr, b))

static __forceinline uint get_long(uae_ptr addr)
{
    return longget(addr);
}
static __forceinline uint get_word(uae_ptr addr)
{
    return wordget(addr);
}
static __forceinline uint get_byte(uae_ptr addr)
{
    return byteget(addr);
}
static __forceinline uint get_longi(uae_ptr addr)
{
    return longgeti(addr);
}
static __forceinline uint get_wordi(uae_ptr addr)
{
    return wordgeti(addr);
}

/*
 * Read a host pointer from addr
 */
#if SIZEOF_VOID_P == 4
    #define get_pointer(addr) ((void*)get_long(addr))
#else
    #if SIZEOF_VOID_P == 8
static __forceinline void* get_pointer(uae_ptr addr)
{
    const uint n = SIZEOF_VOID_P / 4;
    union
    {
        void* ptr;
        uint longs[SIZEOF_VOID_P / 4];
    } p = { 0 };
    uint i;

    for (i = 0; i < n; i++)
    {
        #ifdef WORDS_BIGENDIAN
        p.longs[i] = get_long(addr + i * 4);
        #else
        p.longs[n - 1 - i] = get_long(addr + i * 4);
        #endif
    }
    return p.ptr;
}
    #else
        #error "Unknown or unsupported pointer size."
    #endif
#endif

static __forceinline void put_long(uae_ptr addr, uint l)
{
    longput(addr, l);
}
static __forceinline void put_word(uae_ptr addr, uint w)
{
    wordput(addr, w);
}
static __forceinline void put_byte(uae_ptr addr, uint b)
{
    byteput(addr, b);
}

// extern void put_long_slow(uae_ptr addr, uint v);
// extern void put_word_slow(uae_ptr addr, uint v);
// extern void put_byte_slow(uae_ptr addr, uint v);
// extern uint get_long_slow(uae_ptr addr);
// extern uint get_word_slow(uae_ptr addr);
// extern uint get_byte_slow(uae_ptr addr);

/*
 * Store host pointer v at addr
 */
#if SIZEOF_VOID_P == 4
    #define put_pointer(addr, p) (put_long((addr), (uint)(p)))
#else
    #if SIZEOF_VOID_P == 8
static __forceinline void put_pointer(uae_ptr addr, void* v)
{
    const uint n = SIZEOF_VOID_P / 4;
    union
    {
        void* ptr;
        uint longs[SIZEOF_VOID_P / 4];
    } p;
    uint i;

    p.ptr = v;

    for (i = 0; i < n; i++)
    {
        #ifdef WORDS_BIGENDIAN
        put_long(addr + i * 4, p.longs[i]);
        #else
        put_long(addr + i * 4, p.longs[n - 1 - i]);
        #endif
    }
}
    #endif
#endif

static __forceinline byte* get_real_address(uae_ptr addr)
{
    return get_mem_bank(addr).xlateaddr(addr);
}

static __forceinline int valid_address(uae_ptr addr, uint size)
{
    return get_mem_bank(addr).check(addr, size);
}

extern int addr_valid(TCHAR *, uae_ptr, uint);

/* For faster access in custom chip emulation.  */
extern uint REGPARAM3 chipmem_lget(uae_ptr) REGPARAM;
extern uint REGPARAM3 chipmem_wget(uae_ptr) REGPARAM;
extern uint REGPARAM3 chipmem_bget(uae_ptr) REGPARAM;
extern void REGPARAM3 chipmem_lput(uae_ptr, uint) REGPARAM;
extern void REGPARAM3 chipmem_wput(uae_ptr, uint) REGPARAM;
extern void REGPARAM3 chipmem_bput(uae_ptr, uint) REGPARAM;

extern uint REGPARAM3 chipmem_agnus_lget(uae_ptr) REGPARAM;
extern uint REGPARAM3 chipmem_agnus_wget(uae_ptr) REGPARAM;
extern uint REGPARAM3 chipmem_agnus_bget(uae_ptr) REGPARAM;
extern void REGPARAM3 chipmem_agnus_lput(uae_ptr, uint) REGPARAM;
extern void REGPARAM3 chipmem_agnus_wput(uae_ptr, uint) REGPARAM;
extern void REGPARAM3 chipmem_agnus_bput(uae_ptr, uint) REGPARAM;

extern uint chipmem_mask, kickmem_mask;
extern byte* kickmemory;
extern int kickmem_size;
extern addrbank dummy_bank;

/* 68020+ Chip RAM DMA contention emulation */
extern uint REGPARAM3 chipmem_lget_ce2(uae_ptr) REGPARAM;
extern uint REGPARAM3 chipmem_wget_ce2(uae_ptr) REGPARAM;
extern uint REGPARAM3 chipmem_bget_ce2(uae_ptr) REGPARAM;
extern void REGPARAM3 chipmem_lput_ce2(uae_ptr, uint) REGPARAM;
extern void REGPARAM3 chipmem_wput_ce2(uae_ptr, uint) REGPARAM;
extern void REGPARAM3 chipmem_bput_c2(uae_ptr, uint) REGPARAM;

extern uint (REGPARAM3* chipmem_lget_indirect)(uae_ptr) REGPARAM;
extern uint (REGPARAM3* chipmem_wget_indirect)(uae_ptr) REGPARAM;
extern uint (REGPARAM3* chipmem_bget_indirect)(uae_ptr) REGPARAM;
extern void (REGPARAM3* chipmem_lput_indirect)(uae_ptr, uint) REGPARAM;
extern void (REGPARAM3* chipmem_wput_indirect)(uae_ptr, uint) REGPARAM;
extern void (REGPARAM3* chipmem_bput_indirect)(uae_ptr, uint) REGPARAM;
extern int (REGPARAM2* chipmem_check_indirect)(uae_ptr, uint);
extern byte*(REGPARAM2 * chipmem_xlate_indirect)(uae_ptr);

// #ifdef NATMEM_OFFSET
//
// typedef struct shmpiece_reg
// {
//     byte* native_address;
//     int id;
//     uint size;
//     struct shmpiece_reg* next;
//     struct shmpiece_reg* prev;
// } shmpiece;
//
// extern shmpiece* shm_start;
//
// #endif

extern byte* mapped_malloc(size_t, const TCHAR*);
extern void mapped_free(byte*);
// extern void clearexec();
// extern void mapkick();
extern void a3000_fakekick(int);

extern uae_ptr strcpyha_safe(uae_ptr dst, const char* src);
extern char* strcpyah_safe(char* dst, uae_ptr src, int maxsize);
extern void memcpyha_safe(uae_ptr dst, const byte* src, int size);
extern void memcpyha(uae_ptr dst, const byte* src, int size);
extern void memcpyah_safe(byte* dst, uae_ptr src, int size);
extern void memcpyah(byte* dst, uae_ptr src, int size);